package com.itcam.maker.generator.main;

import cn.hutool.core.io.FileUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.core.util.ZipUtil;
import com.itcam.maker.generator.JarGenerator;
import com.itcam.maker.generator.ScriptGenerator;
import com.itcam.maker.generator.file.DynamicFileGenerator;
import com.itcam.maker.meta.Meta;
import com.itcam.maker.meta.MetaManager;
import freemarker.template.TemplateException;

import java.io.File;
import java.io.IOException;

public abstract class GenerateTemplate {

    public void doGenerate() throws TemplateException, IOException, InterruptedException {
        Meta meta = MetaManager.getMetaObject();

        // 0.输出根路径
        String projectPath = System.getProperty("user.dir");
        String outputPath = projectPath + File.separator + "generated" + File.separator + meta.getName();
        doGenerate(outputPath, meta);
    }

    public void doGenerate(String outputPath, Meta meta) throws IOException, TemplateException, InterruptedException {
        if (!FileUtil.exist(outputPath)) {
            FileUtil.mkdir(outputPath);
        }

        // 1.复制原始文件
        String sourceCopyDestPath = copySource(meta, outputPath);

        // 2.代码生成
        generateCode(meta, outputPath);

        // 3.构建jar包
        String jarPath = buildJar(meta, outputPath);

        // 4.封装脚本
        String shellOutputFilePath = buildScript(outputPath, jarPath);

        // 5.生成精简版的程序——给用户使用
        buildDist(outputPath, sourceCopyDestPath, jarPath, shellOutputFilePath);
    }

    /**
     * 复制原始文件
     *
     * @param meta
     * @param outputPath
     * @return
     */
    protected String copySource(Meta meta, String outputPath) {
        String sourceRootPath = meta.getFileConfig().getSourceRootPath();
        String sourceCopyDestPath = outputPath + File.separator + ".source";
        FileUtil.copy(sourceRootPath, sourceCopyDestPath, false);
        return sourceCopyDestPath;
    }

    /**
     * 🌟 此方法用于根据给定的Meta对象和输出路径生成代码
     * 它会读取资源目录下的模板文件，并根据Meta对象中的数据生成对应的Java文件和pom.xml文件。
     *
     * @param meta       包含代码生成所需元数据的Meta对象
     * @param outputPath 生成代码的输出路径
     * @throws IOException        如果在读取模板文件或写入生成的文件时发生I/O错误
     * @throws TemplateException  如果在处理Freemarker模板时发生错误
     */
    protected void generateCode(Meta meta, String outputPath) throws IOException, TemplateException {
        // 📂 读取 resources 目录，这里暂时为空，后续可根据实际情况设置
        String inputResourcePath = "";

        // 📦 Java包基础路径
        // 从Meta对象中获取基础包名
        String outputBasePackage = meta.getBasePackage();
        // 将基础包名中的点号替换为斜杠，转换为文件路径格式
        String outputBasePackagePath = StrUtil.join("/", StrUtil.split(outputBasePackage, "."));
        // 拼接完整的Java基础包路径
        String outputBaseJavaPackagePath = outputPath + File.separator + "src/main/java/" + outputBasePackagePath;

        // 定义输入文件路径和输出文件路径变量
        String inputFilePath;
        String outputFilePath;

        // 📄 model.DataModel
        // 构建DataModel模板文件的输入路径
        inputFilePath = inputResourcePath + File.separator + "templates/java/model/DataModel.java.ftl";
        // 构建DataModel生成文件的输出路径
        outputFilePath = outputBaseJavaPackagePath + "/model/DataModel.java";
        // 使用DynamicFileGenerator根据模板和Meta数据生成DataModel.java文件
        DynamicFileGenerator.doGenerate(inputFilePath, outputFilePath, meta);

        // 📄 cli.command.ConfigCommand
        inputFilePath = inputResourcePath + File.separator + "templates/java/cli/command/ConfigCommand.java.ftl";
        outputFilePath = outputBaseJavaPackagePath + "/cli/command/ConfigCommand.java";
        DynamicFileGenerator.doGenerate(inputFilePath, outputFilePath, meta);

        // 📄 cli.command.GenerateCommand
        inputFilePath = inputResourcePath + File.separator + "templates/java/cli/command/GenerateCommand.java.ftl";
        outputFilePath = outputBaseJavaPackagePath + "/cli/command/GenerateCommand.java";
        DynamicFileGenerator.doGenerate(inputFilePath, outputFilePath, meta);

        // 📄 cli.command.ListCommand
        inputFilePath = inputResourcePath + File.separator + "templates/java/cli/command/ListCommand.java.ftl";
        outputFilePath = outputBaseJavaPackagePath + "/cli/command/ListCommand.java";
        DynamicFileGenerator.doGenerate(inputFilePath, outputFilePath, meta);

        // 📄 cli.command.JsonGenerateCommand
        inputFilePath = inputResourcePath + File.separator + "templates/java/cli/command/JsonGenerateCommand.java.ftl";
        outputFilePath = outputBaseJavaPackagePath + "/cli/command/JsonGenerateCommand.java";
        DynamicFileGenerator.doGenerate(inputFilePath, outputFilePath, meta);

        // 📄 cli.CommandExecutor
        inputFilePath = inputResourcePath + File.separator + "templates/java/cli/CommandExecutor.java.ftl";
        outputFilePath = outputBaseJavaPackagePath + "/cli/CommandExecutor.java";
        DynamicFileGenerator.doGenerate(inputFilePath, outputFilePath, meta);

        // 📄 Main
        inputFilePath = inputResourcePath + File.separator + "templates/java/Main.java.ftl";
        outputFilePath = outputBaseJavaPackagePath + "/Main.java";
        DynamicFileGenerator.doGenerate(inputFilePath, outputFilePath, meta);

        // 📄 generator.DynamicGenerator
        inputFilePath = inputResourcePath + File.separator + "templates/java/generator/DynamicGenerator.java.ftl";
        outputFilePath = outputBaseJavaPackagePath + File.separator + "/generator/DynamicGenerator.java";
        DynamicFileGenerator.doGenerate(inputFilePath, outputFilePath, meta);

        // 📄 generator.StaticGenerator
        inputFilePath = inputResourcePath + File.separator + "templates/java/generator/StaticGenerator.java.ftl";
        outputFilePath = outputBaseJavaPackagePath + File.separator + "/generator/StaticGenerator.java";
        DynamicFileGenerator.doGenerate(inputFilePath, outputFilePath, meta);

        // 📄 generator.MainGenerator
        inputFilePath = inputResourcePath + File.separator + "templates/java/generator/MainGenerator.java.ftl";
        outputFilePath = outputBaseJavaPackagePath + File.separator + "/generator/MainGenerator.java";
        DynamicFileGenerator.doGenerate(inputFilePath, outputFilePath, meta);

        // 📄 pom.xml
        inputFilePath = inputResourcePath + File.separator + "templates/pom.xml.ftl";
        outputFilePath = outputPath + File.separator + "pom.xml";
        DynamicFileGenerator.doGenerate(inputFilePath, outputFilePath, meta);
    }


    /**
     * 🏭 像个神奇工厂一样构建 JAR 包
     * 这个方法会启动 JarGenerator 这个“小工匠”，让它在指定的输出路径“生产”出 JAR 包。
     * 接着，根据 Meta 对象里的项目名称和版本号，像给产品贴标签一样生成 JAR 包的名字。
     * 最后，告诉我们这个 JAR 包在“target”这个“仓库”里的具体位置。
     *
     * @param meta       包含项目名称和版本信息的 Meta 对象，就像产品的设计蓝图
     * @param outputPath 输出路径，指定 JAR 包生成的“车间”
     * @return JAR 包在“仓库”里的相对路径
     * @throws IOException          如果在“生产” JAR 包时遇到“材料运输”（I/O）问题
     * @throws InterruptedException 如果在“生产”过程中被“外来因素”（线程中断）打扰
     */
    protected String buildJar(Meta meta, String outputPath) throws IOException, InterruptedException {
        // 启动 JarGenerator 这个“小工匠”，开始在指定“车间”生产 JAR 包
        JarGenerator.doGenerate(outputPath);
        // 根据设计蓝图上的名称和版本号，给生产好的 JAR 包贴上专属标签
        String jarName = String.format("%s-%s-jar-with-dependencies.jar", meta.getName(), meta.getVersion());
        // 确定 JAR 包在“仓库”（target 目录）里的具体位置
        String jarPath = "target/" + jarName;
        // 返回 JAR 包的“仓库位置”
        return jarPath;
    }

    /**
     * 📜 像个神秘巫师一样封装脚本
     * 这个方法会先根据输出路径算出脚本文件的“藏身之处”，
     * 然后召唤 ScriptGenerator 这个“魔法助手”，按照 JAR 包的路径施魔法生成脚本文件。
     * 最后，告诉我们这个脚本文件的“藏身之处”。
     *
     * @param outputPath 输出路径，指定脚本文件“安家”的地方
     * @param jarPath    JAR 包的路径，脚本文件要和这个 JAR 包“合作”
     * @return 脚本文件的“安家地址”
     * @throws IOException 如果在“施展魔法”（生成脚本文件）时遇到“魔力波动”（I/O 错误）
     */
    protected String buildScript(String outputPath, String jarPath) throws IOException {
        // 算出脚本文件的“安家地址”，文件名为 generator
        String shellOutputFilePath = outputPath + File.separator + "generator";
        // 召唤 ScriptGenerator 这个“魔法助手”，按照 JAR 包路径施魔法生成脚本文件
        ScriptGenerator.doGenerate(shellOutputFilePath, jarPath);
        // 返回脚本文件的“安家地址”
        return shellOutputFilePath;
    }

    /**
     * 📦 像个专业打包师一样制作压缩包
     * 这个方法会根据传入的输出路径，给它加上 .zip 后缀，就像给包裹贴上“压缩包”的标签。
     * 然后调用 ZipUtil.zip 方法，把输出路径对应的内容“打包”成一个压缩包，就像把东西装进一个箱子里。
     * 最后，返回这个压缩包的路径，就像告诉别人箱子放在哪里。
     *
     * @param outputPath 要压缩的内容所在的路径，就像要打包的东西放在哪里
     * @return 生成的压缩包的路径，就像打包好的箱子放在哪里
     */
    protected String buildZip(String outputPath) {
        // 📋 给输出路径加上 .zip 后缀，确定压缩包的名字
        String zipPath = outputPath + ".zip";
        // 📦 调用 ZipUtil.zip 方法，把输出路径对应的内容“打包”成压缩包
        ZipUtil.zip(outputPath, zipPath);
        // 📍 返回压缩包的路径，告诉别人“箱子”放在哪里
        return zipPath;
    }


    /**
     * 🛠️ 生成精简版程序，就像把一个大工具箱里的常用工具挑出来放到一个小盒子里
     * 此方法会将生成的项目中的关键文件（如 JAR 包、脚本文件、源模板文件）拷贝到一个新的目录中，
     * 形成一个精简版的程序包，方便用户使用。
     *
     * @param outputPath            原始输出路径，就像大工具箱的位置
     * @param sourceCopyDestPath    源模板文件的拷贝目标路径，好比工具箱里的特定工具区域
     * @param jarPath               JAR 包的路径，类似于工具箱里的重要工具
     * @param shellOutputFilePath   脚本文件的路径，如同使用工具的说明书
     * @return 产物包路径，也就是我们挑好工具后放进去的小盒子的位置
     */
    protected String buildDist(String outputPath, String sourceCopyDestPath, String jarPath, String shellOutputFilePath) {
        // 📂 确定精简版程序包的输出路径，在原始输出路径后面加上 -dist，就像给小盒子贴个标签
        String distOutputPath = outputPath + "-dist";
        // 👇 拷贝 jar 包
        // 📁 创建 target 目录，用于存放 JAR 包，就像在小盒子里隔出一个专门放重要工具的小格子
        String targetAbsolutePath = distOutputPath + File.separator + "target";
        FileUtil.mkdir(targetAbsolutePath);
        // 📦 构建 JAR 包的绝对路径，找到大工具箱里重要工具的具体位置
        String jarAbsolutePath = outputPath + File.separator + jarPath;
        // 🚚 把 JAR 包拷贝到 target 目录，将重要工具从小格子转移到小盒子里
        FileUtil.copy(jarAbsolutePath, targetAbsolutePath, true);
        // 👇 拷贝脚本文件
        // 📄 把脚本文件拷贝到精简版程序包目录，将使用工具的说明书也放进小盒子
        FileUtil.copy(shellOutputFilePath, distOutputPath, true);
        // 📄 把 .bat 后缀的脚本文件也拷贝到精简版程序包目录，考虑到不同系统的使用需求
        FileUtil.copy(shellOutputFilePath + ".bat", distOutputPath, true);
        // 👇 拷贝源模板文件
        // 📋 把源模板文件拷贝到精简版程序包目录，将工具箱里的特定工具区域的工具也放进小盒子
        FileUtil.copy(sourceCopyDestPath, distOutputPath, true);
        // 📍 返回精简版程序包的路径，告诉别人小盒子放在哪里
        return distOutputPath;
    }


}
